﻿namespace WpfDevelopment.MVVM.Tests
{
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using System;
    using Moq;
    using System.Dynamic;
    using System.Threading;
    using System.Globalization;
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Resources;
    using System.IO;
    using System.Text;
    using System.Collections;
using System.Collections.Generic;
    
    public class CultureManagerTest
    {
        [TestClass]
        public class Constructor
        {
            [TestMethod, ExpectedException(typeof(ArgumentNullException))]
            public void Must_throw_exception_if_type_is_null()
            {
                // Arrange
                Mock<ICommunicationBus> cbusMock = new Mock<ICommunicationBus>();
                Mock<IResourceManager> rsManagerMock = new Mock<IResourceManager>();
                Mock<ILog> logMock = new Mock<ILog>();
                
                // Act
                var sut = new CultureManager(null, cbusMock.Object, logMock.Object, rsManagerMock.Object);

                // Asert => throw exception attribute
            }

            [TestMethod, ExpectedException(typeof(ArgumentNullException))]
            public void Must_throw_exception_if_communicationBus_is_null()
            {
                // Arrange
                Mock<ILog> logMock = new Mock<ILog>();
                Mock<IResourceManager> rsManagerMock = new Mock<IResourceManager>();

                // Act
                var sut = new CultureManager(typeof(string), null, logMock.Object, rsManagerMock.Object);

                // Asert => throw exception attribute
            }

            [TestMethod, ExpectedException(typeof(ArgumentNullException))]
            public void Must_throw_exception_if_log_is_null()
            {
                // Arrange
                Mock<ICommunicationBus> cbusMock = new Mock<ICommunicationBus>();
                Mock<IResourceManager> rsManagerMock = new Mock<IResourceManager>();

                // Act
                var sut = new CultureManager(typeof(string), cbusMock.Object, null, rsManagerMock.Object);

                // Asert => throw exception attribute
            }

            [TestMethod]
            public void Must_register_to_communicationbus_and_react_on_changeCulture_message()
            {
                // Arrange
                ICommunicationBus cbus = new CommunicationBus();
                Mock<ILog> logMock = new Mock<ILog>();
                Mock<IResourceManager> rsManagerMock = new Mock<IResourceManager>();
                Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-GB");
                var sut = new CultureManager(typeof(string), cbus, logMock.Object, rsManagerMock.Object);

                // Act
                cbus.Notify("ChangeCulture", "nl-NL");

                // Assert
                Assert.AreEqual("nl-NL", Thread.CurrentThread.CurrentUICulture.Name);
            }

            [TestMethod]
            public void Must_invoke_culture_changed_event_when_changeCulture_message_is_retrieved()
            {
                // Arrange
                ICommunicationBus cbus = new CommunicationBus();
                Mock<ILog> logMock = new Mock<ILog>();
                Mock<IResourceManager> rsManagerMock = new Mock<IResourceManager>();
                Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-GB");
                var sut = new CultureManager(typeof(string), cbus, logMock.Object, rsManagerMock.Object);
                var fired = false;
                sut.CultureChangedEvent += (s, e) =>
                {
                    fired = true;
                };

                // Act
                cbus.Notify("ChangeCulture", "nl-NL");

                // Assert
                Assert.IsTrue(fired);
            }

            [TestMethod]
            public void Must_not_invoke_culture_changed_event_when_changeCulture_message_is_retrieved_but_isnt_different_from_current_culture()
            {
                // Arrange
                ICommunicationBus cbus = new CommunicationBus();
                Mock<ILog> logMock = new Mock<ILog>();
                Mock<IResourceManager> rsManagerMock = new Mock<IResourceManager>();
                Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-GB");
                var sut = new CultureManager(typeof(string), cbus, logMock.Object, rsManagerMock.Object);
                bool fired = false;
                sut.CultureChangedEvent += (s, e) =>
                {
                    fired = true;
                };

                // Act
                cbus.Notify("ChangeCulture", "en-GB");

                // Assert
                Assert.AreEqual("en-GB", Thread.CurrentThread.CurrentUICulture.Name);
                Assert.IsFalse(fired);
            }

            [TestMethod]
            public void CultureChangedEvent_must_release_all_resources_of_resourceManager()
            {
                // Arrange
                ICommunicationBus cbus = new CommunicationBus();
                Mock<ILog> logMock = new Mock<ILog>();
                Mock<IResourceManager> rsManagerMock = new Mock<IResourceManager>();

                // TODO: Figure out how resourceset stream should be formatted
                byte[] byteArray = Encoding.ASCII.GetBytes("TODO");
                MemoryStream stream = new MemoryStream(byteArray);
                rsManagerMock
                    .Setup(x => x.GetResourceSet(It.IsAny<CultureInfo>(), true, true))
                    .Returns(new ResourceSetStub(new Dictionary<string,string>()));
                
                Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-GB");
                var sut = new CultureManager(typeof(string), cbus, logMock.Object, rsManagerMock.Object);

                // Act
                cbus.Notify("ChangeCulture", "nl-NL");

                // Assert
                rsManagerMock.Verify(x => x.ReleaseAllResources());
            }
        }

        [TestClass]
        public class TryGetMember
        {
            [TestMethod]
            public void Must_return_false_if_binder_is_null()
            {
                // Arrange
                Mock<ICommunicationBus> cbusMock = new Mock<ICommunicationBus>();
                Mock<ILog> logMock = new Mock<ILog>();
                Mock<IResourceManager> rsManagerMock = new Mock<IResourceManager>();
                var sut = new CultureManager(typeof(string), cbusMock.Object, logMock.Object, rsManagerMock.Object);
                object result;

                // Act
                bool succeed = sut.TryGetMember(null, out result);

                // Assert
                Assert.IsFalse(succeed);
            }

            [TestMethod]
            public void Must_write_exception_to_log_if_resourceManager_throws_ArgumentNullException()
            {
                // Arrange
                Mock<ICommunicationBus> cbusMock = new Mock<ICommunicationBus>();
                Mock<ILog> logMock = new Mock<ILog>();
                Mock<IResourceManager> rsManagerMock = new Mock<IResourceManager>();
                rsManagerMock
                    .Setup(x => x.GetString(It.IsAny<string>()))
                    .Throws<ArgumentNullException>();

                var sut = new CultureManager(typeof(string), cbusMock.Object, logMock.Object, rsManagerMock.Object);
                object result;

                // Act
                bool succeed = sut.TryGetMember(new GetMemberBinderStub("Test"), out result);

                // Assert
                Assert.IsFalse(succeed);
                logMock.Verify(x => x.Write("The name parameter to retrieve cannot be null"));
            }

            [TestMethod]
            public void Must_write_exception_to_log_if_resourceManager_throws_InvalidOperationException()
            {
                // Arrange
                Mock<ICommunicationBus> cbusMock = new Mock<ICommunicationBus>();
                Mock<ILog> logMock = new Mock<ILog>();
                Mock<IResourceManager> rsManagerMock = new Mock<IResourceManager>();
                rsManagerMock
                    .Setup(x => x.GetString(It.IsAny<string>()))
                    .Throws<InvalidOperationException>();

                var sut = new CultureManager(typeof(string), cbusMock.Object, logMock.Object, rsManagerMock.Object);
                object result;

                // Act
                bool succeed = sut.TryGetMember(new GetMemberBinderStub("Test"), out result);

                // Assert
                Assert.IsFalse(succeed);
                logMock.Verify(x => x.Write("The resource requested (Test) is not of the type string"));
            }

            [TestMethod]
            public void Must_write_exception_to_log_if_resourceManager_throws_MissingManifestResourceException()
            {
                // Arrange
                Mock<ICommunicationBus> cbusMock = new Mock<ICommunicationBus>();
                Mock<ILog> logMock = new Mock<ILog>();
                Mock<IResourceManager> rsManagerMock = new Mock<IResourceManager>();
                rsManagerMock
                    .Setup(x => x.GetString(It.IsAny<string>()))
                    .Throws<MissingManifestResourceException>();

                var sut = new CultureManager(typeof(string), cbusMock.Object, logMock.Object, rsManagerMock.Object);
                object result;

                // Act
                bool succeed = sut.TryGetMember(new GetMemberBinderStub("Test"), out result);

                // Assert
                Assert.IsFalse(succeed);
                logMock.Verify(x => x.Write("Neutral culture resources not found"));
            }

            [TestMethod]
            public void Must_return_true_if_resource_can_be_found()
            {
                // Arrange
                Mock<ICommunicationBus> cbusMock = new Mock<ICommunicationBus>();
                Mock<ILog> logMock = new Mock<ILog>();
                Mock<IResourceManager> rsManagerMock = new Mock<IResourceManager>();
                rsManagerMock
                    .Setup(x => x.GetString(It.IsAny<string>()))
                    .Returns(string.Empty);

                var sut = new CultureManager(typeof(string), cbusMock.Object, logMock.Object, rsManagerMock.Object);
                object result;

                // Act
                bool succeed = sut.TryGetMember(new GetMemberBinderStub("Test"), out result);

                // Assert
                Assert.IsTrue(succeed);
            }

        }

        [TestClass]
        public class CultureChangedEvent
        {
            [TestMethod]
            public void Must_fire_property_changed_for_each_item_in_the_resourceset()
            {
                // Arrange
                ICommunicationBus cbus = new CommunicationBus();
                Mock<ILog> logMock = new Mock<ILog>();
                Mock<IResourceManager> rsManagerMock = new Mock<IResourceManager>();
                var resourceSet = new ResourceSetStub (new Dictionary<string,string>
                {
                    {"key1","val1"},
                    {"key2","val2"},
                });
                rsManagerMock
                    .Setup(x => x.GetResourceSet(CultureInfo.CurrentCulture, true, true))
                    .Returns(resourceSet);
                var sut = new CultureManager(typeof(string), cbus, logMock.Object, rsManagerMock.Object);
                List<string> firedFor = new List<string>();
                sut.PropertyChanged += (s, e) =>
                {
                    firedFor.Add(e.PropertyName);
                };

                // Act
                cbus.Notify("ChangeCulture", "nl-NL");

                // Assert
                Assert.AreEqual("key1", firedFor[0]);
                Assert.AreEqual("key2", firedFor[1]);
            }
        }
    }
}

